package foragers;

import java.util.LinkedList;
import java.util.Queue;
import org.eclipse.swt.events.PaintEvent;
import commands.Painter;
import game.Food;
import game.Position;
import strategies.FoodChooser;
//
/**
 * @author Daniel Schwartz 036852242
 */
public class Forager implements Cloneable ,Painter{
	
	/**
	 * the forager's foodchoosing(implementation) strategy
	 * It is a painter that means it can be used by simulation observers to be painted.
	 * See Painter and SimObserver (commands - package)
	 */
	FoodChooser foodChooser;
	
	//will return to 'home' after each food picking
	Position home, current;
	LinkedList<Food> foods;
	
	//current speed, and Max speed.
	int speed, maxSpeed;
	
	
	/**
	 * holds a series of 'step's. that is a virtual(no actual position changed until 'acting' the step)
	 * the q suppose to hold a single plan i.e go to food pick it and return home.  
	 */
	Queue<Action> actionQ = null;
	
	
	/**
	 * Reference to my current food choice
	 * mainly for food position resetting
	 */
	Food choice;
	
	
	
	/**
	 * stName is the strategy name of this forager.
	 */
	String name = null, stName = null;
	
	/**
	 * a list of all the foods the forager has picked (including one that wasn't returned home yet). 
	 */
	LinkedList<Food> picks;
	

	
	
	//----------end data members-----------------------//
	
	/**
	 * Constructor via position, food(array), foodchooser strategy, and limit speed
	 * 
	 * @param pos will be forager's home position
	 * @param foodArr forager's food array to choose from
	 * @param fChooser the implementation of FoodChooser that forager will use to pick a food
	 * @param speed the forager's speed limit
	 */
	public Forager(FoodChooser fChooser, Position pos, LinkedList<Food> foodArr,  int speed){
		
		current = pos;
		home = new Position(current.getX(),current.getY());
		foods = foodArr;
		foodChooser = fChooser;
		maxSpeed = this.speed = speed;
		
		//build containers.
		actionQ = new LinkedList<Action>();
		picks = new LinkedList<Food>();
		
	}
	
	
	
	/**
	 * Cloning this forager.
	 * @return a new forager. almost identical to this.
	 * No actionQ, choice and picks cloning. (since they are run related) !!
	 */
	public Forager clone(){
		
		//new food array clone.
		LinkedList<Food> list = new LinkedList<Food>();
		Forager copy;
		
		//make food array clone.
		for(Food f: foods){
			list.add(f.clone());
		}
		
		//notice foodChooser is shallow copied.
		copy = new Forager(foodChooser, current.clone(), list, speed);
		copy.setName(name);
		copy.setHome(home);
		copy.setStrategyName(stName);
		copy.setMaxSpeed(maxSpeed);
		
		return copy;
	}

	
	
	
	/**
	 * interface for a single action (for actionQ).
	 * represents a single forager action. like step or pickup but only information wise not actual action.
	 * until action() is provoked. see method action, info.
	 */
	private interface Action{
		
		/**
		 * Perform the action. i.e make the real action.
		 * @return true if action succeed. false o.w.
		 */
		public abstract boolean action(); 
	}
	
	
	
	
	/**
	 * implements a single pickup action (for actionQ).
	 * only pickup information. not the actual pickup  
	 * until action() is provoked. see method action, info.
	 */
	private class Pickup implements Action{
		
		//food to pickup
		Food tryFood;
		
		/** 
		 * constructor.
		 * @param food for action to pickup when action() is provoked
		 */
		public Pickup(Food choice) {

			tryFood = choice;
		}
		
		
		
		/**
		 * actually try to pickup the food.
		 * @return true if food was picked. false o.w (like someone else picked the food)
		 */
		@Override
		public boolean action() {
			
			//if able to pickup food. add food to picks List.
			if (pickupFood(tryFood))
			{
				picks.add(tryFood.clone());
				return true;
			}
			
			//couldn't pickup the food. 
			return false;
		}
		
	}
		
	
	
	
	/**
	 * implements a single put down action (for actionQ).
	 * only put down information. not the actual pickup  
	 * until action() is provoked. see method action, info.
	 */
	private class Putdown implements Action{
		
		
		/**
		 * actually try to put down the food.
		 * @return true if food was picked. false  - NEVER.
		 */
		@Override
		public boolean action() {
			
			//simly return to normal speed.
			speed = maxSpeed;
			return true;
		}
	}
	
	
	
	
	/**
	 * implements a single step action (for actionQ). Can be step with food or without.
	 * only step information. not the actual step.   
	 * until action() is provoked. see method action, info.
	 */
	private class Step implements Action{
		
		//target position coordinates.
		int targX, targY;
		/**
		 * will be the forager's Food. if this step is a 'with food step'.
		 *other wise will be the food the forager is aiming for.
		 **/
		Food targetFood;
		boolean withFood;
		
		
		/**
		 * Constructor
		 * @param x step to this x position
		 * @param y step to this y position
		 * @param targetFood if carrying food with this step this should be it.
		 * 	      o.w this is the food the forager is aiming for.
		 * @param withFood mark if this step is a with food step.
		 */
		public Step(int x, int y, Food targetFood, boolean withFood) {
			
			this.targX = x;
			this.targY = y;
			//Note: reference.
			this.targetFood = targetFood;
			this.withFood = withFood;
		}

		
		/**
		 * actually perform the step
		 * @return true if made the step. and the target food was picked up already.
		 * False if made the step and target food wasn't picked up. 
		 */
		public boolean action() {

			//change current position.
			current.setX(targX);
			current.setY(targY);
			 
			//if stepping with food. change also foods position.
			if (withFood)
			{
				targetFood.getPosition().setX(targX);
				targetFood.getPosition().setY(targY);
				return true;
			}
			
			//if this step is "to" a food, check if it's picked
			return (!targetFood.isPickedup());
			
		}
	}


	
	
	
	/*
	 * choose a food that's not picked. 
	 * make virtual Action's to go to it. pick it. return home. and put the food down. 
	 * and put the action's in the member actionQ.
	 */
	private boolean plan()
	{
		//Choose food.
		Food choice = foodChooser.chooseFood(current, foods);
		//if no available foods left.
		if (choice == null)
			return false;
	
		
		/////variables for the "go" to food part:///
		
		Position foodPos = choice.getPosition();
		//forager's distance from the food
		int dis = current.distanceFrom(foodPos);
		//naturally round iteration number
		int iter = (int) (0.5+  ((double) dis /maxSpeed));
		
		int curX,curY, newX = 0, newY = 0;
		
		/////variables for the "go" to home part:///
		double decrease;
		int Vspeed, homX, homY;
		
		/////Go to food planing///:
		
		curX = current.getX();
		curY = current.getY();
		
		for (int i = 0; i < iter; i++)
		{		        
			//x and y delta's: from current to food. cast division to double.  and round naturally(+0.5)(int)
			newX = (int)Math.round( (double)(foodPos.getX() - curX) / (iter - i) ) ;
			newY = (int)Math.round( (double)(foodPos.getY() - curY) / (iter - i) ) ;
	
			//update "current" position
			curX += newX;
			curY += newY;
			
			actionQ.add(new Step(curX,curY, choice, false));	
		}
		
		actionQ.add(new Pickup(choice));
		
		/////go home planing:///
		
		//the distance from food to home
		dis = choice.getPosition().distanceFrom(home);
		
		decrease = choice.getWeight()*0.01*maxSpeed;
		
		//naturally rounded decreasion
		Vspeed = (int)((maxSpeed - decrease) + 0.5);
		
		//round naturraly the iter' number
		iter = (int)(0.5+  ((double) dis /Vspeed));

		homX = home.getX();
		homY = home.getY();
			
		for(int i = 0; i < iter; i++)
		{
			//x and y delta's: from current to home. cast division to double.  and round naturally(+0.5)(int)
			newX = (int)Math.round( (double)(homX - curX) / (iter - i) ) ;
			newY = (int)Math.round( (double)(homY - curY) / (iter - i) ) ;
				
			//update current pos also in locals curx cury.
			curX += newX;
			curY += newY;
			
			actionQ.add(new Step(curX,curY, choice, true));
		}
		
		actionQ.add(new Putdown());

		//actions have been inputed to the queue
		return true;
		
	}
		
	
	
	/**
	 * pop a single action from actionQ. and perform it. 
	 * if actionQ is empty will make a new 'plan' (see plan()) And then perform the first action.
	 * @return false if and only if not other food's to choose from or the action has failed.
	 */
	public boolean act()
	{	
		
		if(actionQ.isEmpty())
		{	
			plan();
			//if ther's no plan to be made
			if (actionQ.isEmpty())
				return false;
		}
		
		//make action. if didn't perform action:
		if (!actionQ.poll().action())
			actionQ.clear();
		
		return true;
		
	}
	
	
	
	/**
	 * marks that food(f) was picked(if succeeded to pick), and decreases forager's speed accordingly.
	 * Notice it is synchronized. so two foragers cannot pickup the same food. 
	 * @param f the food to pickup
	 * @return true if food was really picked, false if was unable to pickup food
	 */
	private boolean pickupFood(Food f)
	{	
		synchronized (f){
			
			if (f.isPickedup())
			{			
				return false;
			}
			else
			{
				//if not picked up. mark that it is now beeing pickd.
				f.setPickedup(true);
				
				//how much speed the forager has to reduce.
				double decrease = f.getWeight()*0.01*speed;
			
				//change speed (naturally rounded dicression)
				speed = (int)((speed - decrease) + 0.5);
		
				return true;
			}
		}
	}
	
	
	
	///////////////////setters//////////////////////////
	
	
	
	/**
	 * set current x and y, position for the fotrager
	 * @param x x location horizontal
	 * @param y y location vertical
	 */
	public void setCurrent(int x, int y) {
		this.current = new Position(x,y);
	}
	
	
	
	/**
	 * set's the forager's name
	 * @param name
	 */
	public void setName(String name) {
		this.name = name;
	}
	
	
	/**
	 * set's the forager's available foods array (will forage these foods)
	 * @param foods 
	 */
	public void setFoods(LinkedList<Food> foods) {
		this.foods = foods;
	}
	
	
	/**
	 * sets the foragers home position. forager will bring food's to this location.
	 * @param home
	 */
	public void setHome(Position home) {
		this.home = home;
	}
	
	
	/**
	 * sets the strategy (FoodChooser) for the forager to pick food's with.
	 * @param stName the strategy exact name.
	 */
	public void setStrategyName(String stName){
		this.stName = stName;
	}
	
	
	/**
	 * sets the forager's max speed. or the speed forager can walk without food holding.
	 * @param maxSpeed the maximum speed.
	 */
	public void setMaxSpeed(int maxSpeed) {
		this.maxSpeed = maxSpeed;
	}
	
	
	
	///////////////////getters//////////////////////////
	
	
	/**
	 * get forager's name
	 * @return forager's name
	 */
	public String getName() {
		return name;
	}
	
	
	/**
	 * get the forager's strategy name	
	 * @return the strategy name forager's choosing food with.
	 */
	public String getStName() {
		return stName;
	}
	
	
	/**
	 * get current speed
	 * @return the current forager's speed
	 */
	public int getSpeed() {
		return speed;
	}
	
	
	/**
	 * get the forager's food linked list, of all food's it has picked.
	 * @return linked list of foods(copies) that forager has picked.
	 */
	public LinkedList<Food> getPicks() {
		return picks;
	}
	
	
	

	@Override
	/**
	 * method can be used by simulation observers to paint a food.
	 * (when redraw(), painter classes can be paint()'ed. )
	 */
	public void paint(PaintEvent e) {
		
		//draw 10 radius 5
		e.gc.drawOval(current.getX(), current.getY() , 10, 10);
	}

	
	
	
}
